// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#pragma once

#include <fbl/array.h>
#include <fbl/unique_fd.h>
#include <fbl/vector.h>
#include <fvm/fvm.h>
#include <stdarg.h>
#include <stdlib.h>

namespace fvm {

// Checker defines a class which may be used to validate an FVM
// (provided as either a regular file or a raw block device).
class Checker {
public:
    Checker();
    Checker(fbl::unique_fd fd, uint32_t block_size, bool silent);
    ~Checker();

    // Sets the path of the block device / image to read the FVM from.
    void SetDevice(fbl::unique_fd fd) {
        fd_ = fbl::move(fd);
    }

    // Sets the block size of the provided device. Not automatically queried from the underlying
    // device, since this checker may operate on a regular file, which does not have an
    // attached block size.
    void SetBlockSize(uint32_t block_size) {
        block_size_ = block_size;
    }

    // Toggles the output of future calls to |Log|.
    void SetSilent(bool silent) {
        logger_.SetSilent(silent);
    }

    // Read from and validate the provided device, logging information if requested.
    bool Validate() const;

private:
    class Logger {
    public:
        Logger() : silent_(false) {};
        explicit Logger(bool silent) : silent_(silent) {};

        // Toggles the output of future calls to |Log|.
        void SetSilent(bool silent) {
            silent_ = silent;
        }

        // Prints the format string and arguments to stderr.
        void Error(const char* format, ...) const {
            va_list arg;
            va_start(arg, format);
            vprintf(format, arg);
            va_end(arg);
        }

        // Prints the format string and arguments to stdout, unless explicitly silenced.
        void Log(const char* format, ...) const {
            va_list arg;
            if (!silent_) {
                va_start(arg, format);
                vprintf(format, arg);
                va_end(arg);
            }
        }

    private:
        bool silent_;
    };

    // Cached information from loading and validating the FVM.
    struct FvmInfo {
        // Contains both copies of metadata.
        fbl::Array<uint8_t> metadata;
        size_t valid_metadata_offset;
        const uint8_t* valid_metadata;
        const uint8_t* invalid_metadata;
        size_t block_size;
        size_t block_count;
        size_t device_size;
        size_t slice_size;
    };

    struct Slice {
        uint64_t virtual_partition;
        uint64_t virtual_slice;
        uint64_t physical_slice;
    };

    struct Partition {
        bool Allocated() const { return entry != nullptr; }

        const fvm::vpart_entry_t* entry = nullptr;
        fbl::Vector<Slice> slices;
    };


    // Parses the FVM info from the device, and validate it (minimally).
    bool LoadFVM(FvmInfo* out) const;

    // Outputs and checks information about the FVM, optionally logging parsed information.
    bool CheckFVM(const FvmInfo& info) const;

    // Acquires a list of slices and partitions while parsing the FVM.
    //
    // Returns false if the FVM contains contradictory or invalid data.
    bool LoadPartitions(const size_t slice_count, const fvm::slice_entry_t* slice_table,
                        const fvm::vpart_entry_t* vpart_table, fbl::Vector<Slice>* out_slices,
                        fbl::Array<Partition>* out_partitions) const;

    // Displays information about |slices|, assuming they are sorted in physical slice order.
    void DumpSlices(const fbl::Vector<Slice>& slices) const;

    // Confirms the Checker has received necessary arguments before beginning validation.
    bool ValidateOptions() const;

    fbl::unique_fd fd_;
    uint32_t block_size_ = 512;
    Logger logger_;
};

} // namespace fvm
